Utforska avancerade JavaScript Proxy-mönster för objektavlyssning, validering och dynamiskt beteende. FörbÀttra kodkvalitet, sÀkerhet och underhÄllbarhet med praktiska exempel.
JavaScript Proxy-mönster: Avancerad objektavlyssning och validering
JavaScript Proxy-objektet Àr en kraftfull funktion som lÄter dig avlyssna och anpassa grundlÀggande objektoperationer. Det möjliggör avancerade metaprogrammeringstekniker, erbjuder större kontroll över objektbeteende och öppnar upp för sofistikerade designmönster. Denna artikel utforskar olika Proxy-mönster, visar deras anvÀndningsfall inom validering, avlyssning och dynamisk beteendemodifikation. Vi kommer att dyka ner i praktiska exempel för att visa hur Proxies kan förbÀttra kodkvalitet, sÀkerhet och underhÄllbarhet i dina JavaScript-projekt.
FörstÄ JavaScript Proxy
I sin kÀrna omsluter ett Proxy-objekt ett annat objekt (mÄlet) och avlyssnar operationer som utförs pÄ det mÄlet. Dessa avlyssningar hanteras av traps, som Àr metoder som definierar anpassat beteende för specifika operationer som att hÀmta en egenskap, stÀlla in en egenskap eller anropa en funktion. Proxy API tillhandahÄller en flexibel och utbyggbar mekanism för att modifiera objekts standardbeteende.
Nyckelbegrepp
- MÄl (Target): Det ursprungliga objektet som Proxyn omsluter.
- Hanterare (Handler): Ett objekt som innehÄller trap-metoderna. Varje trap motsvarar en specifik operation.
- Traps: Metoder inom hanteraren som avlyssnar och anpassar objektoperationer. Vanliga traps inkluderar
get,set,applyochconstruct.
Skapa en Proxy
För att skapa en Proxy anvÀnder du Proxy-konstruktorn, och skickar mÄlobjektet och hanterarobjektet som argument:
const target = {};
const handler = {
get: function(target, property, receiver) {
console.log(`Getting property: ${property}`);
return Reflect.get(target, property, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.name = "John"; // Loggar: Getting property: name
console.log(proxy.name); // Loggar: Getting property: name, sedan John
Vanliga Proxy Traps
Proxies erbjuder en rad traps för att avlyssna olika operationer. HÀr Àr nÄgra av de vanligaste trapsen:
get(target, property, receiver): Avlyssnar egenskapÄtkomst.set(target, property, value, receiver): Avlyssnar egenskapstilldelning.has(target, property): Avlyssnar operatornin.deleteProperty(target, property): Avlyssnar operatorndelete.apply(target, thisArg, argumentsList): Avlyssnar funktionsanrop.construct(target, argumentsList, newTarget): Avlyssnar operatornnew.getPrototypeOf(target): Avlyssnar metodenObject.getPrototypeOf().setPrototypeOf(target, prototype): Avlyssnar metodenObject.setPrototypeOf().isExtensible(target): Avlyssnar metodenObject.isExtensible().preventExtensions(target): Avlyssnar metodenObject.preventExtensions().getOwnPropertyDescriptor(target, property): Avlyssnar metodenObject.getOwnPropertyDescriptor().defineProperty(target, property, descriptor): Avlyssnar metodenObject.defineProperty().ownKeys(target): Avlyssnar metodernaObject.getOwnPropertyNames()ochObject.getOwnPropertySymbols().
Proxy-mönster
LÄt oss nu utforska nÄgra praktiska Proxy-mönster och deras tillÀmpningar:
1. Valideringsproxy
En valideringsproxy upprÀtthÄller begrÀnsningar för egenskapstilldelningar. Den avlyssnar set-trapen för att validera det nya vÀrdet innan tilldelningen tillÄts fortsÀtta.
Exempel: Validering av anvÀndarinmatning i ett formulÀr.
const user = {};
const validator = {
set: function(target, property, value) {
if (property === 'age') {
if (!Number.isInteger(value) || value < 0 || value > 120) {
throw new Error('Invalid age. Age must be an integer between 0 and 120.');
}
}
target[property] = value;
return true; // Indikerar framgÄng
}
};
const proxy = new Proxy(user, validator);
proxy.name = 'Alice';
proxy.age = 30;
console.log(user);
try {
proxy.age = 'invalid'; // Kastar ett fel
} catch (error) {
console.error(error.message);
}
I detta exempel kontrollerar set-trapen om egenskapen age Àr ett heltal mellan 0 och 120. Om valideringen misslyckas, kastas ett fel, vilket förhindrar att det ogiltiga vÀrdet tilldelas.
Globalt exempel: Detta valideringsmönster Àr avgörande för att sÀkerstÀlla dataintegritet i globala applikationer dÀr anvÀndarinmatning kan komma frÄn olika kÀllor och kulturer. Till exempel kan validering av postnummer variera avsevÀrt mellan lÀnder. En valideringsproxy kan anpassas för att stödja olika valideringsregler baserat pÄ anvÀndarens plats.
const address = {};
const addressValidator = {
set: function(target, property, value) {
if (property === 'postalCode') {
// Exempel: Antar en enkel validering av amerikanskt postnummer
if (!/^[0-9]{5}(?:-[0-9]{4})?$/.test(value)) {
throw new Error('Invalid US postal code.');
}
}
target[property] = value;
return true;
}
};
const addressProxy = new Proxy(address, addressValidator);
addressProxy.postalCode = "12345-6789"; // Giltigt
try {
addressProxy.postalCode = "abcde"; // Ogiltigt
} catch(e) {
console.log(e);
}
// För en mer internationell applikation skulle du anvÀnda ett mer sofistikerat valideringsbibliotek
// som kunde validera postnummer baserat pÄ anvÀndarens land.
2. Loggningsproxy
En loggningsproxy avlyssnar egenskapÄtkomst och tilldelning för att logga dessa operationer. Den Àr anvÀndbar för felsökning och granskning.
Exempel: Loggning av egenskapÄtkomst och modifiering.
const data = {
value: 10
};
const logger = {
get: function(target, property) {
console.log(`Getting property: ${property}`);
return target[property];
},
set: function(target, property, value) {
console.log(`Setting property: ${property} to ${value}`);
target[property] = value;
return true;
}
};
const proxy = new Proxy(data, logger);
console.log(proxy.value); // Loggar: Getting property: value, sedan 10
proxy.value = 20; // Loggar: Setting property: value till 20
get- och set-trapsen loggar egenskapen som nÄs eller modifieras, vilket ger en spÄrning av objektinteraktioner.
Globalt exempel: I ett multinationellt företag kan loggningsproxies anvÀndas för att granska dataÄtkomst och modifieringar utförda av anstÀllda pÄ olika platser. Detta Àr avgörande för efterlevnad och sÀkerhetsÀndamÄl. Tidszoner kan behöva beaktas i loggningsinformationen.
const employeeData = {
name: "John Doe",
salary: 50000
};
const auditLogger = {
get: function(target, property) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [GET] Accessing property: ${property}`);
return target[property];
},
set: function(target, property, value) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [SET] Setting property: ${property} to ${value}`);
target[property] = value;
return true;
}
};
const proxiedEmployee = new Proxy(employeeData, auditLogger);
proxiedEmployee.name; // Loggar tidsstÀmpel och Ätkomst till 'name'
proxiedEmployee.salary = 60000; // Loggar tidsstÀmpel och modifiering av 'salary'
3. Skrivskyddad Proxy
En skrivskyddad Proxy förhindrar egenskapstilldelning. Den avlyssnar set-trapen och kastar ett fel om ett försök görs att modifiera en egenskap.
Exempel: Göra ett objekt oförÀnderligt.
const config = {
apiUrl: 'https://api.example.com'
};
const readOnly = {
set: function(target, property, value) {
throw new Error(`Cannot set property: ${property}. Object is read-only.`);
}
};
const proxy = new Proxy(config, readOnly);
console.log(proxy.apiUrl);
try {
proxy.apiUrl = 'https://newapi.example.com'; // Kastar ett fel
} catch (error) {
console.error(error.message);
}
Varje försök att stÀlla in en egenskap pÄ proxyn kommer att resultera i ett fel, vilket sÀkerstÀller att objektet förblir oförÀnderligt.
Globalt exempel: Detta mönster Àr anvÀndbart för att skydda konfigurationsfiler som inte bör modifieras vid körning, sÀrskilt i globalt distribuerade applikationer. Att av misstag modifiera konfigurationen i en region kan pÄverka hela systemet.
const globalSettings = {
defaultLanguage: "en",
currency: "USD",
timeZone: "UTC"
};
const immutableHandler = {
set: function(target, property, value) {
throw new Error(`Cannot modify read-only property: ${property}`);
}
};
const immutableSettings = new Proxy(globalSettings, immutableHandler);
console.log(immutableSettings.defaultLanguage); // visar 'en'
// Försök att Àndra ett vÀrde kommer att kasta ett fel
// immutableSettings.defaultLanguage = "fr"; // kastar Error: Cannot modify read-only property: defaultLanguage
4. Virtuell Proxy
En virtuell Proxy kontrollerar Ätkomsten till en resurs som kan vara kostsam att skapa eller hÀmta. Den kan fördröja skapandet av resursen tills den faktiskt behövs.
Exempel: Lat laddning av en bild.
const image = {
display: function() {
console.log('Displaying image');
}
};
const virtualProxy = {
get: function(target, property) {
if (property === 'display') {
console.log('Creating image...');
const realImage = {
display: function() {
console.log('Displaying real image');
}
};
target.display = realImage.display;
return realImage.display;
}
return target[property];
}
};
const proxy = new Proxy(image, virtualProxy);
// Bilden skapas inte förrÀn display anropas.
proxy.display(); // Loggar: Creating image..., sedan Displaying real image
Det verkliga bildobjektet skapas endast nÀr metoden display anropas, vilket undviker onödig resursförbrukning.
Globalt exempel: ĂvervĂ€g en global e-handelswebbplats som visar produktbilder. Genom att anvĂ€nda en virtuell Proxy kan bilder laddas endast nĂ€r de Ă€r synliga för anvĂ€ndaren, vilket optimerar bandbreddsanvĂ€ndningen och förbĂ€ttrar sidladdningstiderna, sĂ€rskilt för anvĂ€ndare med lĂ„ngsamma internetanslutningar i olika regioner.
const product = {
loadImage: function() {
console.log("Loading high-resolution image...");
// Simulerar laddning av en stor bild
setTimeout(() => {
console.log("Image loaded");
this.displayImage();
}, 2000);
},
displayImage: function() {
console.log("Displaying the image");
}
};
const lazyLoadProxy = {
get: function(target, property) {
if (property === "displayImage") {
// IstÀllet för att ladda omedelbart, fördröj laddningen
console.log("Request to display image received. Loading...");
target.loadImage();
return function() { /* Intentionally Empty */ }; // Returnera tom funktion för att förhindra omedelbar exekvering
}
return target[property];
}
};
const proxiedProduct = new Proxy(product, lazyLoadProxy);
// Anrop displayImage utlöser den lata laddningsprocessen
proxiedProduct.displayImage();
5. Ă terkallningsbar Proxy
En Äterkallningsbar Proxy lÄter dig Äterkalla proxyn nÀr som helst, vilket gör den oanvÀndbar. Detta Àr anvÀndbart för sÀkerhetskÀnsliga scenarier dÀr du behöver kontrollera Ätkomsten till ett objekt.
Exempel: Bevilja tillfÀllig Ätkomst till en resurs.
const target = {
secret: 'This is a secret'
};
const handler = {
get: function(target, property) {
console.log('Accessing secret property');
return target[property];
}
};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.secret); // Loggar: Accessing secret property, sedan This is a secret
revoke();
try {
console.log(proxy.secret); // Kastar ett TypeError
} catch (error) {
console.error(error.message); // Loggar: Cannot perform 'get' on a proxy that has been revoked
}
Metoden Proxy.revocable() skapar en Äterkallningsbar proxy. Att anropa funktionen revoke() gör proxyn oanvÀndbar, vilket förhindrar ytterligare Ätkomst till mÄlobjektet.
Globalt exempel: I ett globalt distribuerat system kan du anvÀnda en Äterkallningsbar proxy för att bevilja tillfÀllig Ätkomst till kÀnslig data till en tjÀnst som körs i en specifik region. Efter en viss tid kan proxyn Äterkallas för att förhindra obehörig Ätkomst.
const sensitiveData = {
apiKey: "SUPER_SECRET_KEY"
};
const handler = {
get: function(target, property) {
console.log("Accessing sensitive data");
return target[property];
}
};
const { proxy: dataProxy, revoke: revokeAccess } = Proxy.revocable(sensitiveData, handler);
// TillÄt Ätkomst i 5 sekunder
setTimeout(() => {
revokeAccess();
console.log("Access revoked");
}, 5000);
// Försök att komma Ät data
console.log(dataProxy.apiKey); // Loggar API-nyckeln
// Efter 5 sekunder kommer detta att kasta ett fel
setTimeout(() => {
try {
console.log(dataProxy.apiKey); // Kastar: TypeError: Cannot perform 'get' on a proxy that has been revoked
} catch (error) {
console.error(error);
}
}, 6000);
6. Typkonverteringsproxy
En typkonverteringsproxy avlyssnar egenskapÄtkomst för att automatiskt konvertera det returnerade vÀrdet till en specifik typ. Detta kan vara anvÀndbart nÀr man arbetar med data frÄn olika kÀllor som kan ha inkonsekventa typer.
Exempel: Konvertera strÀngvÀrden till nummer.
const data = {
price: '10.99',
quantity: '5'
};
const typeConverter = {
get: function(target, property) {
const value = target[property];
if (typeof value === 'string' && !isNaN(Number(value))) {
return Number(value);
}
return value;
}
};
const proxy = new Proxy(data, typeConverter);
console.log(proxy.price + 1); // Loggar: 11.99 (nummer)
console.log(proxy.quantity * 2); // Loggar: 10 (nummer)
get-trapen kontrollerar om egenskapens vÀrde Àr en strÀng som kan konverteras till ett nummer. Om sÄ Àr fallet konverterar den vÀrdet till ett nummer innan det returneras.
Globalt exempel: NÀr man hanterar data som kommer frÄn API:er med olika formateringskonventioner (t.ex. olika datumformat eller valutasymboler), kan en typkonverteringsproxy sÀkerstÀlla datakonsistens i din applikation, oavsett kÀlla. Till exempel, hantera olika datumformat och konvertera dem alla till ISO 8601-format.
const apiData = {
dateUS: "12/31/2023",
dateEU: "31/12/2023"
};
const dateFormatConverter = {
get: function(target, property) {
let value = target[property];
if (property.startsWith("date")) {
// Försök att konvertera bÄde amerikanska och EU-datumformat till ISO 8601
if (property === "dateUS") {
const [month, day, year] = value.split("/");
value = `${year}-${month}-${day}`;
} else if (property === "dateEU") {
const [day, month, year] = value.split("/");
value = `${year}-${month}-${day}`;
}
return value;
}
return value;
}
};
const proxiedApiData = new Proxy(apiData, dateFormatConverter);
console.log(proxiedApiData.dateUS); // Utdata: 2023-12-31
console.log(proxiedApiData.dateEU); // Utdata: 2023-12-31
BÀsta metoder för att anvÀnda Proxies
- AnvÀnd Proxies omdömesfullt: Proxies kan lÀgga till komplexitet i din kod. AnvÀnd dem endast nÀr de ger betydande fördelar, sÄsom förbÀttrad validering, loggning eller kontroll över objektbeteende.
- ĂvervĂ€g prestanda: Proxy traps kan medföra overhead. Profilera din kod för att sĂ€kerstĂ€lla att Proxies inte negativt pĂ„verkar prestandan, sĂ€rskilt i prestandakritiska sektioner.
- Hantera fel elegant: Se till att dina trap-metoder hanterar fel pÄ lÀmpligt sÀtt, och tillhandahÄller informativa felmeddelanden nÀr det behövs.
- AnvÀnd Reflect API:
ReflectAPI tillhandahÄller metoder som speglar standardbeteendet för objektoperationer. AnvÀndReflect-metoder inom dina trap-metoder för att delegera till det ursprungliga beteendet nÀr det Àr lÀmpligt. Detta sÀkerstÀller att dina traps inte bryter befintlig funktionalitet. - Dokumentera dina Proxies: Dokumentera tydligt syftet och beteendet för dina Proxies, inklusive de traps som anvÀnds och de begrÀnsningar som upprÀtthÄlls. Detta kommer att hjÀlpa andra utvecklare att förstÄ och underhÄlla din kod.
Slutsats
JavaScript Proxies Àr ett kraftfullt verktyg för avancerad objektmanipulation och avlyssning. Genom att förstÄ och tillÀmpa olika Proxy-mönster kan du förbÀttra kodkvalitet, sÀkerhet och underhÄllbarhet. FrÄn att validera anvÀndarinmatning till att kontrollera Ätkomst till kÀnsliga resurser, erbjuder Proxies en flexibel och utbyggbar mekanism för att anpassa objektbeteende. NÀr du utforskar möjligheterna med Proxies, kom ihÄg att anvÀnda dem omdömesfullt och dokumentera din kod noggrant.
De tillhandahÄllna exemplen visar hur man anvÀnder JavaScript Proxies för att lösa verkliga problem i en global kontext. Genom att förstÄ och tillÀmpa dessa mönster kan du skapa mer robusta, sÀkra och underhÄllbara applikationer som möter behoven hos en mÄngsidig anvÀndarbas. Kom ihÄg att alltid övervÀga de globala implikationerna av din kod och anpassa dina lösningar till de specifika kraven för olika regioner och kulturer.